#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>

class ThreadPool 
{
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    //返回类型后置语法
    //std::future是 C++11 引入的异步编程组件，用于存储异步任务的结果
    //在 auto enqueue(F&& f, Args&&... args) -> ... 中，
    //右值引用的体现需要结合 “通用引用”（Universal Reference） 的特性来理解 —— 因为这是模板函数，
    //参数中的 && 并非单纯的 “右值引用”，而是根据实参的 “值类别”（左值 / 右值）动态推导为左值引用或右值引用，其本质是通过右值引用语法实现的 “通用引用”，最终目的是支持完美转发
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    // need to keep track of threads so we can join them
    std::vector< std::thread > workers;
    // the task queue
    std::queue< std::function<void()> > tasks;
    
    // synchronization
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};
 
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
    :   stop(false)//表示线程池刚创建时处于运行状态
{
    //创建指定数量的工作线程，并让这些线程进入 “等待 - 执行” 循环
    for(size_t i = 0;i<threads;++i)
        workers.emplace_back(// 每个循环创建一个工作线程
            [this]
            {
                for(;;)
                { // 无限循环：线程启动后一直运行，直到线程池停止
                    std::function<void()> task;
                    { // 控制lock的生命周期
                       
                     // 加锁：确保对任务队列的操作线程安全
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        
                     //等待条件：释放锁并阻塞，直到满足“停止信号”或“有任务”
                     //condition.wait(lock, 谓词) 的底层逻辑其实是一个 “循环等待”
                        this->condition.wait(lock,
                            [this]
                            {
                                 //先释放锁并休眠，直到被唤醒后重新检查等待条件，条件满足才继续执行
                                  //等待条件：停止线程池 或 任务队列不为空
                                  //当 “有任务” 或 “线程池要停止” 这两个条件中的任意一个满足时，线程就会结束休眠，继续执行后续逻辑（要么执行任务，要么退出）
                                 return this->stop || !this->tasks.empty();
                                 });

                     // 3. 检查是否需要退出（若线程池停止且队列空，直接退出循环）
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                        //避免任务执行的长时间占用锁，让其他线程可以同时取任务，提高并发效率。
                    task();//任务执行时已释放锁
                }
            }
        );
}


//将用户提交的任务封装后加入线程池的任务队列
//并返回一个std::future对象供调用者获取任务结果
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) 
    -> std::future<typename std::result_of<F(Args...)>::type>  //返回类型后置                 
{
    using return_type = typename std::result_of<F(Args...)>::type;
    //
    auto task = std::make_shared< std::packaged_task<return_type()> >(
        //实现完美转发—— 保持f和args的原始值类别（左值 / 右值），
        //避免不必要的拷贝（例如，若参数是临时对象，forward会将其作为右值传递，触发移动构造而非拷贝）
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        //通过std::bind绑定用户传入的任务函数和参数
    std::future<return_type> res = task->get_future();
    {
        // 加锁保护队列操作
        std::unique_lock<std::mutex> lock(queue_mutex);

        //禁止向已停止的线程池提交任务
        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
        //emplace 方法直接在队列中构造一个任务对象（避免额外拷贝）。
       //传入的参数是一个 lambda 表达式 [task](){ (*task)(); }：这是最终放入队列的任务，
       //执行时会调用之前封装好的 std::packaged_task（即执行用户提交的原始任务 f(args...)）
        tasks.emplace([task](){ (*task)(); });
// 1. task对象的本质
// 在enqueue函数中，task是通过std::make_shared< std::packaged_task<return_type()> >创建的一个智能指针，
// 指向std::packaged_task类型的对象。std::packaged_task是 C++ 标准库中用于包装可调用对象（如函数、lambda 表达式等）的类模板，它能够将任务与异步结果关联起来。
// std::packaged_task 包装了用户提交的任务（通过std::bind绑定用户传入的任务函数和参数），当调用 std::packaged_task 对象时，就会执行被包装的任务 

// 2. (*task)();的具体作用
// 解引用指针：task是一个std::shared_ptr类型的智能指针，*task是对这个智能指针进行解引用操作，获取到指针所指向的std::packaged_task对象。
// 调用任务：(*task)() 相当于调用了std::packaged_task对象的函数调用运算符operator() 。
// 当执行(*task)()时，会触发std::packaged_task执行其内部所包装的用户任务 ，即最初通过std::bind绑定的任务函数（例如用户提交的某个业务逻辑函数）以及对应的参数

    }
    condition.notify_one();//只唤醒一个正在等待的线程（随机或按调度顺序）
    return res;
}

// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    //notify_all，唤醒所有休眠的工作线程，让它们重新检查等待条件，并决定是否继续执行
    //为什么唤醒后要 “重新检查？
    //避免 “假唤醒”：
    //假唤醒指的是线程即使没有收到notify信号，也有可能苏醒。这可能会导致等待条件被提前满足（比如本例中的stop被修改为true了），
    //但是此时可能实际的任务并没有被完成。因此在进入等待状态后再次检查任务是否完成，可以避免这一类问题
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}

#endif